﻿using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Text;
using System.Xml;
using System.Xml.Linq;
using System.Xml.Schema;

namespace WMM.Tapioca.Utilities
{
    /// <summary>
    ///  check two lists for equality 
    /// </summary>
    public static class MyExtensions
    {
        /// <summary>
        /// Check two List for equality.
        /// </summary>
        public static bool Matches<T>(this List<T> list1, List<T> list2)
        {
            if (list1.Count != list2.Count) return false;

            for (var i = 0; i < list1.Count; i++)
            {
                XElement e1 = list1[i] as XElement;
                XElement e2 = list2[i] as XElement;

                if (!XElement.DeepEquals(e1, e2)) return false;
            }
            return true;
        }

        /// <summary>
        /// Try to get the a value of element .
        /// </summary>
        public static bool TryGetValue(this XElement parent, string elementName,
                                                     out string value)
        {
            var xel = parent.Element(elementName);
            if (xel == null)
            {
                value = null;
                return false;
            }
            value = xel.Value;
            return true;
        }

        /// <summary>
        /// Convert XmlNode to XElement .
        /// </summary>
        public static XElement ToXElement(this XmlNode node)
        {
            XDocument xDoc = new XDocument();
            using (XmlWriter xmlWriter = xDoc.CreateWriter())
                node.WriteTo(xmlWriter);
            return xDoc.Root;
        }

        /// <summary>
        /// Convert XElement to XmlNode .
        /// </summary>
        public static XmlNode ToXmlNode(this XElement element)
        {
            using (XmlReader xmlReader = element.CreateReader())
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlReader);
                return xmlDoc;
            }
        }

        /// <summary>
        /// Convert XElement to XmlElement .
        /// </summary>
        public static XmlElement ToXmlElement(this XElement el)
        {
            var doc = new XmlDocument();
            doc.Load(el.CreateReader());
            return doc.DocumentElement;
        }

        /// <summary>
        /// Add extension method to create a new IEnumerable with the added value.
        /// </summary>
        public static IEnumerable<T> Add<T>(this IEnumerable<T> e, T value)
        {
            foreach (var cur in e)
            {
                yield return cur;
            }

            yield return value;
        }

        /// <summary>
        /// Add extension method to create a new IEnumerable with the added IEnumerable.
        /// </summary>
        public static IEnumerable<T> AddIEnumerable<T>(this IEnumerable<T> e, IEnumerable<T> v)
        {
            foreach (var cur in e)
            {
                yield return cur;
            }

            foreach (var cur in v)
            {
                yield return cur;
            }
        }


        /// <summary>
        /// Finds an attribute with the given local name and returns the
        /// attribute of the value
        /// </summary>
        internal static string GetAttributeValue(this XElement xElement, string localName)
        {
            string attrValue = "";

            foreach (XAttribute attr in xElement.Attributes())
            {
                if (attr.Name.LocalName.Equals(localName))
                {
                    attrValue = attr.Value;
                }
            }

            return attrValue;
        }


        internal static void RemoveWithNextWhitespace(this XElement element)
        {
            IEnumerable<XNode> textNodes
                = element.NodesAfterSelf().TakeWhile(node => node is XText);

            if (element.ElementsAfterSelf().Any())
            {
                // Easy case, remove following text nodes.
                textNodes.ToList().ForEach(node => node.Remove());
            }
            else
            {
                // Remove trailing whitespace.
                textNodes.Cast<XText>().TakeWhile(text => !text.Value.Contains("\n"))
                         .ToList().ForEach(text => text.Remove());
                // Fetch text node containing newline, if any.
                XText newLineTextNode
                    = element.NodesAfterSelf().OfType<XText>().FirstOrDefault();

                if (newLineTextNode != null)
                {
                    string value = newLineTextNode.Value;
                    if (value.Length > 1)
                    {
                        // Composite text node, trim until newline (inclusive).
                        newLineTextNode.AddAfterSelf(
                            new XText(value.Substring(value.IndexOf('\n') + 1)));
                    }
                    // Remove original node.
                    newLineTextNode.Remove();
                }
            }

            element.Remove();
        }


        internal static string ToStringAlignAttributes(this XDocument document)
        {
            XmlWriterSettings settings = new XmlWriterSettings();
            settings.Indent = true;
            settings.OmitXmlDeclaration = true;
            settings.NewLineOnAttributes = true;
            StringBuilder stringBuilder = new StringBuilder();
            using (XmlWriter xmlWriter = XmlWriter.Create(stringBuilder, settings))
                document.WriteTo(xmlWriter);
            return stringBuilder.ToString();
        }


        internal static XmlNode GetXmlNode(this XElement element)
        {
            using (XmlReader xmlReader = element.CreateReader())
            {
                XmlDocument xmlDoc = new XmlDocument();
                xmlDoc.Load(xmlReader);
                return xmlDoc;
            }
        }


        internal static string GetQName(XElement xe)
        {
            string prefix = xe.GetPrefixOfNamespace(xe.Name.Namespace);
            if (xe.Name.Namespace.Equals(string.Empty) || prefix == null)
                return xe.Name.LocalName.ToString();
            else
                return prefix + ":" + xe.Name.LocalName.ToString();
        }


        internal static string GetQName(XAttribute xa)
        {
            string prefix =
                xa.Parent.GetPrefixOfNamespace(xa.Name.Namespace);

            if (xa.Name.Namespace.Equals(string.Empty) || prefix == null)
                return xa.Name.ToString();
            else
                return prefix + ":" + xa.Name.LocalName;
        }


        /// <summary>
        /// The methos that creates the namespaces.
        /// </summary>
        public static XmlNamespaceManager CreateNamespaceManager(this XDocument doc)
        {
            XmlNamespaceManager nsmgr = new XmlNamespaceManager(doc.CreateReader().NameTable);

            foreach (XAttribute attr in doc.Root.Attributes())
            {
                if (attr.IsNamespaceDeclaration)
                {
                    string localName = attr.Name.LocalName;

                    if (localName.Equals("xmlns"))
                    {
                        localName = string.Empty;
                    }
                    nsmgr.AddNamespace(localName, attr.Value);
                }
            }

            return nsmgr;
        }

        /// <summary>
        /// The methos verify if exist a namespace.
        /// </summary>
        public static bool HasNamespace(this XElement element, string namespaceToVerify)
        {
            bool found = false;

            foreach (XElement currentElement in element.DescendantsAndSelf())
            {
                if (currentElement.Name.NamespaceName.Equals(namespaceToVerify))
                {
                    found = true;
                    return found;
                }
            }

            return found;
        }


        /// <summary>
        /// The method that verify if the IEnumerable has same items.
        /// </summary>
        public static bool IsSame(this IEnumerable<XElement> elements, IEnumerable<XElement> otherElements)
        {
            bool structureEquals = true;

            if (elements == null && otherElements == null)
                return true;
            if (elements == null || otherElements == null)
                return false;

            List<XElement> list1 = elements.ToList();
            List<XElement> list2 = otherElements.ToList();

            if (list1.Count != list2.Count)
            {
                return false;
            }

            for (int i = 0; i < elements.Count(); i++)
            {
                if (!(elements.ElementAt(i).Name.Equals(otherElements.ElementAt(i).Name) && elements.ElementAt(i).Value.Equals(otherElements.ElementAt(i).Value)))
                    structureEquals = false;
            }

            return structureEquals;
        }


        /// <summary>
        /// The method that verify if the IEnumerable has same items.
        /// </summary>
        public static bool HaveSameItems<T>(this IEnumerable<T> self, IEnumerable<T> other)
        {
            return !
            (
                other.Except(self).Any() ||
                self.Except(other).Any()
            );
        }        


        internal static XElement GetXElement(this XmlNode node)
        {
            XDocument xDoc = new XDocument();
            using (XmlWriter xmlWriter = xDoc.CreateWriter())
                node.WriteTo(xmlWriter);
            return xDoc.Root;
        }

        internal static void RemoveWithNextWhitespace(this XComment element)
        {
            IEnumerable<XNode> textNodes
                = element.NodesAfterSelf().TakeWhile(node => node is XText);

            if (element.ElementsAfterSelf().Any())
            {
                // Easy case, remove following text nodes.
                textNodes.ToList().ForEach(node => node.Remove());
            }
            else
            {
                // Remove trailing whitespace.
                textNodes.Cast<XText>().TakeWhile(text => !text.Value.Contains("\n"))
                         .ToList().ForEach(text => text.Remove());
                // Fetch text node containing newline, if any.
                XText newLineTextNode
                    = element.NodesAfterSelf().OfType<XText>().FirstOrDefault();
                if (newLineTextNode != null)
                {
                    string value = newLineTextNode.Value;
                    if (value.Length > 1)
                    {
                        // Composite text node, trim until newline (inclusive).
                        newLineTextNode.AddAfterSelf(
                            new XText(value.Substring(value.IndexOf('\n') + 1)));
                    }
                    // Remove original node.
                    newLineTextNode.Remove();
                }
            }

            element.Remove();
        }


        /// <summary>
        /// Computing a Cartesian Product with LINQ.
        /// </summary>
        public static IEnumerable<IEnumerable<T>> CartesianProduct<T>(this IEnumerable<IEnumerable<T>> sequences)
        {
            IEnumerable<IEnumerable<T>> emptyProduct = new[] { Enumerable.Empty<T>() };

            if (sequences.Count() == 0)
            {
                return sequences;
            }

            return sequences.Aggregate(
              emptyProduct,
              (accumulator, sequence) =>
                from accseq in accumulator
                from item in sequence
                select accseq.Concat(new[] { item }));
        }


        internal static void RemoveComments(XmlNode xmlNode)
        {
            XmlNodeList commentList = xmlNode.SelectNodes("//comment()");
            foreach (XmlNode node in commentList)
            {
                node.ParentNode.RemoveChild(node);
            }
        }


        # region string extension methods

        public static string GetLocalName(this string valueQN)
        {
            string[] dimAttrQN = valueQN.Split(':');

            if (dimAttrQN.Count() > 1)
            {
                return dimAttrQN[1];
            }
            else
            {
                return dimAttrQN[0];
            }

        }

        public static string GetPrefix(this string qname)
        {
            string[] values = qname.Split(':');

            if (values.Count() > 1)
            {
                return values[0];
            }
            else
            {
                return "";
            }
        }

        private static string GetPath(this string uriBase)
        {
            string path = uriBase;
            int LastPathSeparator = uriBase.LastIndexOf('/');

            if (LastPathSeparator == -1)
            {
                LastPathSeparator = uriBase.LastIndexOf('\\');
                if (LastPathSeparator != -1)
                {
                    path = uriBase.Substring(0, LastPathSeparator + 1);
                }
            }

            path = uriBase.Substring(0, LastPathSeparator + 1);
            return path;
        }

        #endregion
    }
}
